package main

import (
	"flag"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/fsnotify/fsnotify"
)

var (
	//

	ignoreDirs  = []string{"./vendor", "./git"}            //忽略的文件夹
	watchExts   = []string{".go", ".html"}                 //文件夹里包含这些类型文件则监控此文件夹
	ignoreFiles = []string{".git", ".pid", ".exe", "mrun"} //忽略这些文件的变动

	doEvent      chan fsnotify.Event //
	mixUpdateTm  = int64(1)          //最小更新间隔
	lastUpdateTm = int64(0)          //上次更新时间
	cmdStr       = ""                //输入的命令字符串
	cmds         []*exec.Cmd         //命令组
	lastCmd      *exec.Cmd           //最后一个命令
	lock         sync.Mutex          //lock
)

func init() {
	doEvent = make(chan fsnotify.Event)
}

func main() {
	//   ./mrun.exe -d . -c 'go build ,./download.exe -t api' -t 2
	d := flag.String("d", ".", "监控的根文件")
	c := flag.String("c", "", "命令字符串, ','分隔")
	t := flag.Int64("t", 1, "最小更新间隔时间")
	flag.Parse()

	rootDir := *d
	cmdStr = *c
	mixUpdateTm = *t

	//----------------------------
	//把文件夹末尾的/去掉
	if rootDir == "/" {
		log.Println("卡死?")
	}
	if len(rootDir) > 0 && rootDir[len(rootDir)-1] == '/' {
		rootDir = rootDir[0 : len(rootDir)-1]
	}
	for i := 0; i < len(ignoreDirs); i++ {
		str := ignoreDirs[i]
		if len(str) > 0 && str[len(str)-1] == '/' {
			ignoreDirs[i] = str[0 : len(str)-1]
		}
	}

	//----------------------------

	go letCall()
	notifyEvent(fsnotify.Event{})

	var paths []string
	getWatchDirs(rootDir, &paths)
	log.Println(paths)

	watchDir(paths)
}

//通知文件变动
func notifyEvent(e fsnotify.Event) {

	lock.Lock()
	currTm := time.Now().Unix()
	if (lastUpdateTm + mixUpdateTm) > currTm {
		lock.Unlock()
		return
	}
	lastUpdateTm = currTm
	lock.Unlock()

	log.Println("letCall:", e)
	doEvent <- e
}

//执行命令
func letCall() {

	for {

		<-doEvent

		//每次重构命令
		cmds = parseCmds(cmdStr)

		//每次kill最后一个命令
		if lastCmd != nil {
			if err := lastCmd.Process.Kill(); err != nil {
				log.Println("x", err)
			}
			lastCmd = nil
		}

		length := len(cmds)
		for i := 0; i < length; i++ {
			c := cmds[i]
			c.Stdout = os.Stdout
			c.Stderr = os.Stderr

			if i < (length - 1) {
				//阻塞执行完毕
				err := c.Run()
				if err != nil {
					log.Println(err)
				}
				log.Println(c.ProcessState.String())
				//前面的命令必须执行成功
				if c.ProcessState.Success() == false {
					return
				}

			} else {
				//最后一个命令不阻塞
				err := c.Start()
				if err != nil {
					log.Println(err)
				}
				lastCmd = c
			}
		}

	}
}

//监控文件变动
func watchDir(paths []string) {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		log.Fatal(err)
	}
	defer watcher.Close()

	done := make(chan bool)
	go func() {
		for {
			select {
			case event, ok := <-watcher.Events:
				if !ok {
					return
				}

				if checkIgnoreFiles(event.Name) {
					continue
				}
				notifyEvent(event)
			case err, ok := <-watcher.Errors:
				if !ok {
					return
				}
				log.Println("error:", err)
			}
		}
	}()

	for _, dir := range paths {
		err := watcher.Add(dir)
		if err != nil {
			log.Fatal(err)
		}
	}

	<-done
}

//-----------------------------------------------------------------------------

//解析输入的命令
func parseCmds(s string) []*exec.Cmd {
	cmds := make([]*exec.Cmd, 0)

	cmdStrs := strings.Split(s, ",")
	for _, cmdstr := range cmdStrs {
		cmdArray := strings.Split(cmdstr, " ")
		if len(cmdArray) > 0 {
			name := cmdArray[0]
			arg := make([]string, 0)
			for i := 1; i < len(cmdArray); i++ {
				arg = append(arg, cmdArray[i])
			}
			cmds = append(cmds, exec.Command(name, arg...))
		}
	}

	return cmds
}

//获取需要监控的文件夹
func getWatchDirs(directory string, paths *[]string) {
	fileInfos, err := ioutil.ReadDir(directory)
	if err != nil {
		log.Println("err:", err)
		return
	}

	useDirectory := false
	for _, fileInfo := range fileInfos {
		//log.Println(fileInfo.Name())
		if fileInfo.IsDir() && checkIgnoreDirs(directory+"/"+fileInfo.Name()) {
			continue
		}

		if fileInfo.IsDir() && fileInfo.Name()[0] != '.' {
			getWatchDirs(directory+"/"+fileInfo.Name(), paths)
			continue
		}

		if useDirectory {
			continue
		}

		if checkWatchExts(filepath.Ext(fileInfo.Name())) {
			*paths = append(*paths, directory)
			useDirectory = true
		}
	}
}

func checkIgnoreDirs(s string) bool {
	for _, v := range ignoreDirs {
		if v == s {
			return true
		}
	}

	return false
}

func checkWatchExts(s string) bool {
	for _, v := range watchExts {
		if v == s {
			return true
		}
	}

	return false
}

func checkIgnoreFiles(s string) bool {

	for _, v := range ignoreFiles {
		if strings.Index(s, v) > -1 {
			return true
		}
	}

	return false
}
